/*
 * Copyright@2009 FPI,Inc. All rights reserved.
 */
package org.firegrid.core.spring;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.UnsatisfiedDependencyException;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext;
import org.zkoss.zkplus.spring.SpringUtil;

/**
 * @author 邱昌进(changjin_qiu@fpi-inc.com)
 * @create 2009-10-27
 * @since 1.0
 */
public class SpringObjectFactory implements ApplicationContextAware {

	private static final Log log = LogFactory.getLog(SpringObjectFactory.class);

	private ApplicationContext appContext;

	private AutowireCapableBeanFactory autoWiringFactory;

	private int autowireStrategy = AutowireCapableBeanFactory.AUTOWIRE_BY_NAME;

	private SpringObjectFactory() {

	}

	private static SpringObjectFactory factory;

	public static SpringObjectFactory getFactory() {
		if (factory == null) {
			factory = new SpringObjectFactory();
			factory.setApplicationContext(SpringUtil.getApplicationContext());
			factory
					.setAutowireStrategy(AutowireCapableBeanFactory.AUTOWIRE_BY_NAME);
		}
		return factory;
	}

	/**
	 * Set the Spring ApplicationContext that should be used to look beans up
	 * with.
	 * 
	 * @param appContext
	 *            The Spring ApplicationContext that should be used to look
	 *            beans up with.
	 */
	public void setApplicationContext(ApplicationContext appContext)
			throws BeansException {
		this.appContext = appContext;
		findAutoWiringBeanFactory(this.appContext);
	}

	/**
	 * Sets the autowiring strategy
	 * 
	 * @param autowireStrategy
	 */
	public void setAutowireStrategy(int autowireStrategy) {
		switch (autowireStrategy) {
		case AutowireCapableBeanFactory.AUTOWIRE_AUTODETECT:
			log.info("Setting autowire strategy to autodetect");
			this.autowireStrategy = autowireStrategy;
			break;
		case AutowireCapableBeanFactory.AUTOWIRE_BY_NAME:
			log.info("Setting autowire strategy to name");
			this.autowireStrategy = autowireStrategy;
			break;
		case AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE:
			log.info("Setting autowire strategy to type");
			this.autowireStrategy = autowireStrategy;
			break;
		case AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR:
			log.info("Setting autowire strategy to constructor");
			this.autowireStrategy = autowireStrategy;
			break;
		default:
			throw new IllegalStateException("Invalid autowire type set");
		}
	}

	public int getAutowireStrategy() {
		return autowireStrategy;
	}

	/**
	 * If the given context is assignable to AutowireCapbleBeanFactory or
	 * contains a parent or a factory that is, then set the autoWiringFactory
	 * appropriately.
	 * 
	 * @param context
	 */
	private void findAutoWiringBeanFactory(ApplicationContext context) {
		if (context instanceof AutowireCapableBeanFactory) {
			// Check the context
			autoWiringFactory = (AutowireCapableBeanFactory) context;
		} else if (context instanceof ConfigurableApplicationContext) {
			// Try and grab the beanFactory
			autoWiringFactory = ((ConfigurableApplicationContext) context)
					.getBeanFactory();
		} else if (context.getParent() != null) {
			// And if all else fails, try again with the parent context
			findAutoWiringBeanFactory(context.getParent());
		}
	}

	/**
	 * Looks up beans using Spring's application context
	 * 
	 * @param beanName
	 *            The name of the bean to look up in the application context
	 * @return A bean from Spring or the result of calling the overridden
	 *         method.
	 * @throws Exception
	 */
	public Object buildBean(String beanName) throws Exception {
		try {
			return appContext.getBean(beanName);
		} catch (NoSuchBeanDefinitionException e) {
			return null;
		}
	}

	/**
	 * @param clazz
	 * @throws Exception
	 */
	public Object buildBean(Class clazz) throws Exception {
		Object bean = null;

		try {
			bean = autoWiringFactory.autowire(clazz,
					AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR, false);
		} catch (UnsatisfiedDependencyException e) {
			// Fall back
		}

		bean = autoWiringFactory.applyBeanPostProcessorsBeforeInitialization(
				bean, bean.getClass().getName());
		// We don't need to call the init-method since one won't be registered.
		bean = autoWiringFactory.applyBeanPostProcessorsAfterInitialization(
				bean, bean.getClass().getName());

		return autoWireBean(bean);
	}

	/**
	 * @param bean
	 * @return
	 */
	public Object autoWireBean(Object bean) {
		if (autoWiringFactory != null) {
			autoWiringFactory.autowireBeanProperties(bean, autowireStrategy,
					false);
		}
		if (bean instanceof ApplicationContextAware) {
			((ApplicationContextAware) bean).setApplicationContext(appContext);
		}

		return bean;
	}

	public static void autowire(Object bean) {
		SpringObjectFactory sof = getFactory();
		sof.autoWireBean(bean);
	}

}
